﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MentalAlchemy.Atomics;

namespace MentalAlchemy.Molecules
{
	/// <summary>
	/// List of commands.
	/// </summary>
	public static class Commands
	{
		//public static string ASSIGN = "asgn";
		//public static string ADD = "add";
		public static string EXIT = "exit";
		public static string EXPRESSION = "expr";
		public static string FUNCTION_CALL = "call";
		public static string JUMP = "jmp";
		public static string JUMPIFZERO = "jz";
		public static string MOVE = "mov";
		public static string NOP = "nop";
		public static string POP = "pop";
		public static string PUSH = "push";
		//public static string SUB = "sub";
	}

	/// <summary>
	/// [molecule]
	/// 
	/// Class for compiler's command.
	/// </summary>
	[Serializable]
	public class Command
	{
		public string Name;		// command's name.
		public Variable[] Args;	// arguments.
		public string Label;	// command's label (for jumps).

		#region - Public methods. -
		public override string ToString()
		{
			var res = string.IsNullOrEmpty(Label) ? "" : Label + ":";
			res += "\t" + Name;
			if (Args != null)
			{
				var args = "";
				foreach (var arg in Args)
				{
					args +=  "\t" + arg.Name;
				}
				res += args;
			}
			return res;
		} 
		#endregion

		#region - Static methods. -
		/// <summary>
		/// [molecule]
		/// 
		/// Creates assignment command.
		/// </summary>
		/// <param name="lhs"></param>
		/// <param name="rhs"></param>
		/// <returns></returns>
		public static Command Assignment (string lhs, string rhs)
		{
			var res = new Command();
			res.Name = Commands.MOVE;

			var args = new List<Variable>();
			args.Add(new Variable{Name = lhs});
			args.Add(new Variable{Name = rhs});
			res.Args = args.ToArray();
			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Creates function call command.
		/// </summary>
		/// <param name="name"></param>
		/// <param name="args"></param>
		/// <returns></returns>
		public static Command Call(string name, Variable[] args)
		{
			var res = new Command();
			res.Name = Commands.FUNCTION_CALL;

			var arg = new Variable { Name = name };
			var args1 = new List<Variable>(args);
			args1.Insert(0, arg);

			res.Args = args1.ToArray();
			return res;
		}

		public static Command Exit()
		{
			var res = new Command();
			res.Name = Commands.EXIT;
			return res;
		}

		public static Command Jump(string label)
		{
			var res = new Command();
			res.Name = Commands.JUMP;

			// argument.
			var arg = new Variable();
			arg.Name = label;
			res.Args = new []{arg};

			return res;
		}

		public static Command JumpIfZero(string label)
		{
			var res = new Command();
			res.Name = Commands.JUMPIFZERO;

			// argument.
			var arg = new Variable();
			arg.Name = label;
			res.Args = new[] { arg };

			return res;
		}

		public static Command Nop()
		{
			var res = new Command();
			res.Name = Commands.NOP;
			return res;
		}

		public static Command Pop(string val)
		{
			var res = new Command();
			res.Name = Commands.POP;

			// argument.
			var arg = new Variable();
			arg.Name = val;
			res.Args = new[] { arg };

			return res;
		}

		public static Command Push(string val)
		{
			var res = new Command();
			res.Name = Commands.PUSH;

			// argument.
			var arg = new Variable();
			arg.Name = val;
			res.Args = new[] { arg };

			return res;
		}
		#endregion
	}

	#region - Node compilers. -
	/// <summary>
	/// [molecule]
	/// 
	/// Class for conversion of the parse tree node into a commands sequence.
	/// </summary>
	public abstract class TreeNodeCompiler
	{
		public int Id;	// counter for the compiler call.
		public abstract List<Command> Compile (ParseTreeNode node, out string label);

		public static string NextLabel(string prefix, int idx)
		{
			return prefix + (idx + 1);
		}
	}

	public class AssignmentNodeCompiler : TreeNodeCompiler
	{
		#region Overrides of TreeNodeCompiler
		public override List<Command> Compile(ParseTreeNode node, out string label)
		{
			var res = new List<Command>();

			// the 2nd child is an assignment operator (can be skipped).
			// the 3rd child is an Expression.
			var rhs = node.ChildNodes[2];
			var bnfName = rhs.Bnf.Name;

			var tempLbl = "";
			if (bnfName == BNFExpression.DEFAULT_EXPRESSION_BNF_NAME)
			{
				var exprComp = new ExpressionNodeCompiler();
				res.AddRange(exprComp.Compile(node.ChildNodes[2], out tempLbl));
			}
			else if (bnfName == BNFExpression.DEFAULT_FUNCCALL_BNF_NAME)
			{
				var funcComp = new FunctionCallNodeCompiler();
				res.AddRange(funcComp.Compile(node.ChildNodes[2], out tempLbl));
			}
			else
			{
				res.AddRange(ScriptCompiler.Compile(node.ChildNodes[2], out tempLbl));
			}

			// the 1st child is a LHS.
			var lhs = node.ChildNodes[0].Contents;
			res.Add(Command.Assignment(lhs, ScriptExecutor.DEFAULT_COMMAND_RES_VAR));

			label = "";
			return res;
		}
		#endregion
	}

	public class ConditionNodeCompiler : TreeNodeCompiler
	{
		public const string LABEL_PREFIX = "cond";	// prefix for labels for inner jumps.

		#region Overrides of TreeNodeCompiler
		/// note: Problem for embedded conditions!
		/// note: This method is called twice and shares the same ExitLabel.
		public override List<Command> Compile(ParseTreeNode node, out string label)
		{
			var res = new List<Command>();

			// compute condition.
			var exprCom = new ExpressionNodeCompiler();
			var tempLbl = "";
			var condCmds = exprCom.Compile(node.ChildNodes[0], out tempLbl);
			res.AddRange(condCmds);

			string labelPrefix = LABEL_PREFIX + Id;
			//ExitLabel = labelPrefix;	// set exit label.
			label = labelPrefix;	// set exit label.
			
			// increase inner counter.
			Id++;

			string elseLabel = "";
			int count = 0;

			// todo: insert [jz] if there are alternative branches.
			if (node.ChildNodes.Count > 3)
			{	// if there are alternative branches.
				elseLabel = NextLabel(labelPrefix, count);
				res.Add(Command.JumpIfZero(elseLabel));
				++count;
			}
			else
			{	// no [else] branches.
				res.Add(Command.JumpIfZero(label));
			}

			// add direct branch.
			var seqComp = new SequenceNodeCompiler();
			res.AddRange(seqComp.Compile(node.ChildNodes[1], out tempLbl));

			if (node.ChildNodes.Count > 3)
			{	// there is an [else] branch.
				res.Add(Command.Jump(label));
				
				// compile [else] branch.
				var elseBr = seqComp.Compile(node.ChildNodes[3], out tempLbl);
				// set label at the first command.
				elseBr[0].Label = elseLabel;
				res.AddRange(elseBr);
			}
			return res;
		}
		#endregion
	}

	public class FunctionCallNodeCompiler : TreeNodeCompiler
	{
		#region Overrides of TreeNodeCompiler
		public override List<Command> Compile(ParseTreeNode node, out string label)
		{
			// get and push arguments.
			var res = new List<Command>();
			var args = new List<Variable>();
			if (node.ChildNodes.Count > 1)
			{	// if there are arguments.
				foreach (var argNode in node.ChildNodes[1].ChildNodes)
				{
					//var tempLbl = "";
					//var arg = ScriptCompiler.Compile(argNode, out tempLbl);
					//res.AddRange(arg);
					//res.Add(Command.Push(ScriptExecutor.DEFAULT_COMMAND_RES_VAR));
					args.Add(new Variable{Name = argNode.ChildNodes[0].Contents});
				}
			}

			var funcName = node.ChildNodes[0].Token.Text;
			var funcCall = Command.Call(funcName, args.ToArray());
			res.Add(funcCall);
			
			// todo: find out how to define whether function returns anything or not.
			// note: probable solution: *always* to return (even an empty value).

			label = "";
			return res;
		}
		#endregion
	}

	public class HeaderCompiler : TreeNodeCompiler
	{
		#region Overrides of TreeNodeCompiler

		public override List<Command> Compile(ParseTreeNode node, out string label)
		{
			// extract arguments names.
			var res = new List<Command>();
			if (node.ChildNodes.Count > 1)
			{	// if there are arguments.
				foreach (var arg in node.ChildNodes[1].ChildNodes)
				{
					var pop = Command.Pop(arg.ChildNodes[0].Contents);
					res.Add(pop);
				}
			}

			label = "";
			return res;
		}

		#endregion
	}

	public class ExpressionNodeCompiler : TreeNodeCompiler
	{
		#region Overrides of TreeNodeCompiler
		public override List<Command> Compile(ParseTreeNode node, out string label)
		{
			var res = new List<Command>();
			res.Add(new Command());
			res[0].Name = Commands.EXPRESSION;

			var args = new List<Variable>();
			foreach (var childNode in node.ChildNodes)
			{
				args.Add(new Variable { Name = childNode.Contents });
			}
			res[0].Args = args.ToArray();

			label = "";
			return res;
		}
		#endregion
	}

	public class ListItemCompiler : TreeNodeCompiler
	{
		#region Overrides of TreeNodeCompiler
		public override List<Command> Compile(ParseTreeNode node, out string label)
		{
			// note: it works only if item is an ID.
			var res = new List<Command>();
			res.Add(Command.Assignment(ScriptExecutor.DEFAULT_COMMAND_RES_VAR, node.ChildNodes[0].Contents));
			label = "";
			return res;
		}
		#endregion
	}

	public class LoopNodeCompiler : TreeNodeCompiler
	{
		public const string LABEL_PREFIX = "loop";

		#region Overrides of TreeNodeCompiler
		public override List<Command> Compile(ParseTreeNode node, out string label)
		{
			var res = new List<Command>();

			// set exit label.
			string labelPrefix = LABEL_PREFIX + Id;
			label = labelPrefix;	// set exit label.
			Id++;	// increase inner counter.

			var exprComp = new ExpressionNodeCompiler();
			var tempLbl = "";
			// compile condition.
			var cond = exprComp.Compile(node.ChildNodes[0], out tempLbl);
			var loopStartLbl = labelPrefix + "_0";
			cond[0].Label = loopStartLbl;
			res.AddRange(cond);

			// add exit command.
			res.Add(Command.JumpIfZero(label));

			var seqComp = new SequenceNodeCompiler();
			res.AddRange(seqComp.Compile(node.ChildNodes[1], out tempLbl));

			// todo: resolve [break] and [continue] commands, which can set their own exit labels.
			// todo: current implementation deals with [continue] only.
			var endLoop = Command.Jump(loopStartLbl);
			if (!string.IsNullOrEmpty(tempLbl))
			{
				endLoop.Label = tempLbl;
			}
			res.Add(endLoop);	// go to the beginning of the cycle.

			// add NOP command for breaking the loop.
			var nop = Command.Nop();
			nop.Label = label;
			res.Add(nop);

			label = "";
			return res;
		}
		#endregion
	}

	public class SequenceNodeCompiler : TreeNodeCompiler
	{
		#region Overrides of TreeNodeCompiler
		public override List<Command> Compile(ParseTreeNode node, out string label)
		{
			var res = new List<Command>();
			label = "";
			//var comp = new ScriptCompiler();
			foreach (var childNode in node.ChildNodes)
			{
				// skip SeqItem.
				// todo: redo using exitlabel as an argument.
				var tempLbl = "";
				var temp = ScriptCompiler.Compile(childNode.ChildNodes[0], out tempLbl);
				if (temp != null)
				{
					res.AddRange(temp);

					if (!string.IsNullOrEmpty(label))
					{
						temp[0].Label = label;
						//label = ScriptCompiler.HotLabel = "";	// reset label.
						label = "";	// reset label.
					}
				}

				//var bnf = childNode.ChildNodes[0].Bnf;
				//if (bnf != null && bnf.Name == BNFExpression.DEFAULT_CONDITION_BNF_NAME)
				if (!string.IsNullOrEmpty(tempLbl))
				{	// get BNF label.
					label = tempLbl;
				}
			}
			return res;
		}
		#endregion
	} 
	#endregion

	/// <summary>
	/// [molecule]
	/// 
	/// Class to contained compiled script commands.
	/// </summary>
	[Serializable]
	public class CompiledScript
	{
		public List<Command> Commands = new List<Command>();
		public string Name = "";
		public string Path = "";
	}

	public class ScriptCompiler
	{
		public const string LABEL_PREFIX = "lbl";
		//public static Stack<string> Labels = new Stack<string>();
		protected static int labelIdx;
		//public static string HotLabel;	// label which should be set for the next compiled command.

		/// <summary>
		/// List of tree node converters indexed by BNFs.
		/// </summary>
		public static Dictionary<string, TreeNodeCompiler> TreeNodeCompilers;

		public ScriptCompiler ()
		{
			TreeNodeCompilers = new Dictionary<string, TreeNodeCompiler>();

			// assign default converters.
			TreeNodeCompilers.Add(BNFExpression.DEFAULT_ASSIGN_BNF_NAME, new AssignmentNodeCompiler());
			TreeNodeCompilers.Add(BNFExpression.DEFAULT_CONDITION_BNF_NAME, new ConditionNodeCompiler());
			TreeNodeCompilers.Add(BNFExpression.DEFAULT_EXPRESSION_BNF_NAME, new ExpressionNodeCompiler());
			TreeNodeCompilers.Add(BNFExpression.DEFAULT_FUNCCALL_BNF_NAME, new FunctionCallNodeCompiler());
			TreeNodeCompilers.Add(BNFExpression.DEFAULT_HEADER_BNF_NAME, new HeaderCompiler());
			TreeNodeCompilers.Add(BNFExpression.DEFAULT_LISTITEM_BNF_NAME, new ListItemCompiler());
			TreeNodeCompilers.Add(BNFExpression.DEFAULT_LOOP_BNF_NAME, new LoopNodeCompiler());
			TreeNodeCompilers.Add(BNFExpression.DEFAULT_SEQUENCE_BNF_NAME, new SequenceNodeCompiler());
		}

		public TreeNodeCompiler GetNodeCompiler (string name)
		{
			TreeNodeCompiler temp;
			if (name == BNFExpression.DEFAULT_ASSIGN_BNF_NAME)
			{
				temp = new AssignmentNodeCompiler();
			}
			else if (name == BNFExpression.DEFAULT_CONDITION_BNF_NAME)
			{
				temp = new AssignmentNodeCompiler();
			}
			else if (name == BNFExpression.DEFAULT_EXPRESSION_BNF_NAME)
			{
				temp = new AssignmentNodeCompiler();
			}
			else if (name == BNFExpression.DEFAULT_FUNCCALL_BNF_NAME)
			{
				temp = new AssignmentNodeCompiler();
			}
			else if (name == BNFExpression.DEFAULT_LOOP_BNF_NAME)
			{
				temp = new AssignmentNodeCompiler();
			}
			else if (name == BNFExpression.DEFAULT_SEQUENCE_BNF_NAME)
			{
				temp = new AssignmentNodeCompiler();
			}
			else
			{
				return null;
			}
			temp.Id = ContextRandom.Next();
			return temp;
		}

		public static CompiledScript Compile (ParseTree tree)
		{
			labelIdx = 0;
			var label = "";
			var res = new CompiledScript();
			var header = Compile(tree.Nodes[0].ChildNodes[0], out label);

			res.Commands = Compile(tree.Nodes[0].ChildNodes[1], out label);
			res.Commands.InsertRange(0, header);
			res.Name = tree.Name;

			var exit = Command.Exit();
			//
			// check, whether the last compiled node has an ExitLabel.
			if (!string.IsNullOrEmpty(label))
			{
				exit.Label = label;
			}

			res.Commands.Add(exit);
			return res;
		}

		public static List<Command> Compile(ParseTreeNode node, out string label)
		{
			var res = new List<Command>();

			//
			var bnf = node.Bnf;
			//var token = node.Token;

			if (bnf == null)
			{
				// todo: find out what to do with BNFless nodes.
				// they are:
				// - identifiers.
				// - expression operators.
				// - (probably) keywords.
				//return null;
				throw new NotImplementedException("[Compile]: bnf == null");
			}
			else
			{
				if (TreeNodeCompilers.ContainsKey(bnf.Name))
				{
					res = TreeNodeCompilers[bnf.Name].Compile(node, out label);
				}
				else
				{
					throw new Exception("Can't find handler for the node: " + bnf.Name);
				}
			}

			return res;
		}

		public static List<string> ToStrings (List<Command> cmds)
		{
			var res = new List<string> ();

			foreach (var cmd in cmds)
			{
				res.Add(cmd.ToString());
			}

			return res;
		}
	}
}
